﻿using Microsoft.Extensions.Logging;
using System;
using System.Threading.Tasks;

namespace BJoin.DapperExtension.DatabaseFactory
{
    public class DataProvider<TDatabase> : IDataProvider<TDatabase>
        where TDatabase : IDisposable
    {
        private readonly ILogger _logger;
        private readonly IFactory<TDatabase> _factory;

        public DataProvider(ILogger<DataProvider<TDatabase>> logger, IFactory<TDatabase> factory)
        {
            _logger = logger;
            _factory = factory;
        }

        public async Task<TReturn> UsingAsync<TReturn>(Func<TDatabase, Task<TReturn>> func)
        {
            return await _factory.Using(async db => await func(db));
        }

        public async Task UsingAsync(Func<TDatabase, Task> func)
        {
            await _factory.Using(async db => await func(db));
        }
    }

    public class ShardingDataProvider<TDatabase, TShardingKey> : IShardingDataProvider<TDatabase, TShardingKey>
        where TDatabase : IDisposable
    {
        private readonly ILogger _logger;
        protected readonly IDbFactory<TDatabase, TShardingKey> _factory;
        public ShardingDataProvider(ILogger<ShardingDataProvider<TDatabase, TShardingKey>> logger, IDbFactory<TDatabase, TShardingKey> factory)
        {
            _logger = logger;
            _factory = factory;
        }
        //
        public virtual async Task<TReturn> UsingAsync<TReturn>(Func<TDatabase, Task<TReturn>> func)
        {
            return await _factory.Using(async db => await func(db));
        }

        public virtual async Task UsingAsync(Func<TDatabase, Task> func)
        {
            await _factory.Using(async db => await func(db));
        }

        public virtual async Task<TReturn> UsingAsync<TReturn>(Func<TDatabase, Task<TReturn>> func, ReadWrite readWrite)
        {
            using var dataProvider = _factory.Create(readWrite);
            return await func(dataProvider);
        }
        public virtual async Task UsingAsync(Func<TDatabase, Task> func, ReadWrite readWrite)
        {
            using var dataProvider = _factory.Create(readWrite);
            await func(dataProvider);
        }
        //
        public virtual async Task<TReturn> UsingAsync<TReturn>(Func<TDatabase, Task<TReturn>> func, ReadWrite readWrite, TShardingKey shardingKey)
        {
            using var dataProvider = _factory.Create(readWrite, shardingKey);
            return await func(dataProvider);
        }

        public virtual async Task UsingAsync(Func<TDatabase, Task> func, ReadWrite readWrite, TShardingKey shardingKey)
        {
            using var dataProvider = _factory.Create(readWrite, shardingKey);
            await func(dataProvider);
        }

        public virtual async Task<TReturn> ReadAsync<TReturn>(Func<TDatabase, Task<TReturn>> func)
        {
            return await UsingAsync(func, ReadWrite.Read);
        }

        public async Task ReadAsync(Func<TDatabase, Task> func)
        {
            await UsingAsync(func, ReadWrite.Read);
        }

        public virtual async Task<TReturn> ReadAsync<TReturn>(Func<TDatabase, Task<TReturn>> func, TShardingKey shardingKey)
        {
            return await UsingAsync(func, ReadWrite.Read, shardingKey);
        }

        public async Task ReadAsync(Func<TDatabase, Task> func, TShardingKey shardingKey)
        {
            await UsingAsync(func, ReadWrite.Read, shardingKey);
        }

        public async Task WriteAsync(Func<TDatabase, Task> func, TShardingKey shardingKey)
        {
            await UsingAsync(func, ReadWrite.Write, shardingKey);
        }

        public virtual async Task<TReturn> WriteAsync<TReturn>(Func<TDatabase, Task<TReturn>> func)
        {
            return await UsingAsync(func, ReadWrite.Write);
        }

        public async Task WriteAsync(Func<TDatabase, Task> func)
        {
            await UsingAsync(func, ReadWrite.Write);
        }

        public virtual async Task<TReturn> WriteAsync<TReturn>(Func<TDatabase, Task<TReturn>> func, TShardingKey shardingKey)
        {
            return await UsingAsync(func, ReadWrite.Write, shardingKey);
        }
    }
}
